package org.codetranslators.compiler.abstractassembly;

import java.util.Stack;

import org.codetranslators.common.Label;
import org.codetranslators.common.Temp;

public class AssemBinOpExp extends AssemExp
{	
	private int op;
	private AssemExp left, right;
	
	public final static int PLUS =0;
	public final static int MINUS=1;
	public final static int MUL=2;
	public final static int DIV=3;
	public final static int AND=4;
	public final static int OR=5;
	public final static int LSHIFT=6;
	public final static int RSHIFT=7;
	public final static int ARSHIFT=8;
	public final static int XOR=9;
	
	// Logical operators
	public final static int  EQ = 10;
	public final static int  NE = 11;
	public final static int  GT = 12;
	public final static int  LT = 13;
	public final static int  LTE = 14;
	public final static int  GTE = 15;
	
	public AssemBinOpExp(int b, AssemExp l, AssemExp r) 
	{
		op = b;
		left = l; 
		right = r; 
	}
	
	public AssemExpList children() 
	{
		return new AssemExpList(left, new AssemExpList(right,null));
	}
	
	public int getOperator()
	{
		return op;
	}
	
	public AssemType translateToCJump(Label labelTrue, Label labelFalse)
	{
		AssemType returnAssemType = null;
		
		if(op == AssemBinOpExp.EQ || op == AssemBinOpExp.NE 
				|| op == AssemBinOpExp.LT || op == AssemBinOpExp.GT
				|| op == AssemBinOpExp.GTE || op == AssemBinOpExp.LTE)
		{
			int cjmpOp = getCJumpOperator(op);
			
			// Make sure that the left side is a temporary, since mips comparison operators
			// must take place on at least one register
			left = (AssemSeqExp) left.translateToIntType();
			
			returnAssemType = new AssemCJump(cjmpOp, left, right, labelTrue, labelFalse);			
		}
		else 
		{
			// Move this value to a temporary
			Temp temp = new Temp();
			AssemTemp assemTemp = new AssemTemp(temp);
			AssemMove assemMov = new AssemMove(assemTemp, this);
			
			// Create a constant 0
			AssemConst assemConst0 = new AssemConst(0);
			
			// Create a conditional based on whether the above temporary is 1 or 0
			AssemCJump tempCJump = new AssemCJump(AssemCJump.GT, assemTemp, assemConst0,
					labelTrue, labelFalse);
			
			Stack seqStack = new Stack();
			seqStack.push(assemMov);
			seqStack.push(tempCJump);
			
			returnAssemType = translateSeqStatement(seqStack);		
		}
		
		return returnAssemType;
	}
	
	/**
	 * This function will coerce this binary operator expression into an int type,
	 * by creating a temporary holding an int value
	 */
	public AssemType translateToIntType()
	{
		AssemType retAssemType = null;
		
		Temp temp = new Temp();
		AssemTemp assemTemp = new AssemTemp(temp);
		AssemConst assemConst0 = new AssemConst(0);
		AssemConst assemConst1 = new AssemConst(1);
		
		// The left must be an integer type
		left = (AssemExp)left.translateToIntType();
		
		if(op == EQ || op == NE || op == GT || op == LT || op == LTE || op == GTE)
		{
			// These are comparison types; create a temporary and load it with either 0 or 1
			// depending on the result
			Label trueLabel = new Label();
			Label falseLabel = new Label();
			Label joinLabel = new Label();
		
			// Move constant 1 to a temporary
			AssemMove assemMove1 = 	new AssemMove(assemTemp, assemConst1);

			// Create a new Conditional based on the comparison
			int cjumpOp = getCJumpOperator(op);
			AssemCJump assemCJump = new AssemCJump(cjumpOp, 
					left, right, trueLabel, falseLabel); 
			
			// Create a new false label, needs to be followed by a move statement to move 0 to the temp.
			AssemLabel assemLabel1  = new AssemLabel(falseLabel);
			
			// Create the "false" path, ie, assigning 0 to the temporary
			AssemMove assemMove2 = new AssemMove(assemTemp, assemConst0);
			
			// ... and the true label
			AssemLabel assemLabel2  = new AssemLabel(trueLabel);
			
			// The jump statement for the join label
			AssemLabel assemJoinLabel = new AssemLabel(joinLabel);
			AssemName assemNameJoinLabel = new AssemName(joinLabel);
			AssemJump assemJumpJoin = new AssemJump(assemNameJoinLabel);
			
			Stack seqStack = new Stack();
			seqStack.push(assemCJump);    // The conditional first
			seqStack.push(assemLabel2);   // The true label
			seqStack.push(assemMove1);    // Move 1 into the temp since the condition is true
			seqStack.push(assemJumpJoin);   // Jump to the join label
			seqStack.push(assemLabel1);   // The false label
			seqStack.push(assemMove2);    // Move 0 into the temp since the condition is false
			seqStack.push(assemJoinLabel);          // The join label

			
			AssemStm seqStm = translateSeqStatement(seqStack);
			retAssemType = new AssemSeqExp(seqStm, assemTemp);
		}
		else
		{
			// These are binary arithmetic operators; move the result to a temporary 
			// and return it; before that, make sure that the left and right side are 
			// converted to int types
			
			//left = (MipsAssemExp)left.translateToIntType(assemTreeBuilder);
			//right = (MipsAssemExp)right.translateToIntType(assemTreeBuilder);
			
			AssemMove assemMove = new AssemMove(assemTemp, this);
			
			retAssemType = new AssemSeqExp(assemMove, assemTemp); 
		}
		return retAssemType;
	}
	
	public int getType()
	{
		if(op == EQ || op == NE || op == GT|| op == LT || op == LTE || op == GTE)
			return COMPARE_TYPE;
		else
			return INT_TYPE;
	}
	
	public String getDescription()
	{
		String retStr = "BinOpExp(";
		String opStr = "";
		switch(op)
		{
		case EQ:
			opStr = "=";
			break;
		case NE:
			opStr = "<>";
			break;
		case GT:
			opStr = ">";
			break;
		case LT:
			opStr = "<";
			break;
		case GTE:
			opStr = ">=";
			break;
		case LTE:
			opStr = "<=";
			break;
		case PLUS:
			opStr = "+";
			break;
		case MINUS:
			opStr = "-";
			break;
		case MUL:
			opStr = "*";
			break;
		case DIV:
			opStr = "/";
			break;
		case AND:
			opStr = "&";
			break;
		case OR:
			opStr = "|";
			break;
		case LSHIFT:
			opStr = "LSHIFT";
			break;
		case RSHIFT:
			opStr = "RSHIFT";
			break;
		case ARSHIFT:
			opStr = "ARSHIFT";
			break;
		case XOR:
			opStr = "XOR";
			break;	
		}
		
		return retStr + opStr + ")";
	}

	public AssemExp getLeft() {
		return left;
	}

	public AssemExp getRight() {
		return right;
	}
	
	private int getCJumpOperator(int op)
	{
		if(op == EQ) return AssemCJump.EQ;
		else if(op == NE) return AssemCJump.NE;
		else if(op == GT) return AssemCJump.GT;
		else if(op == LT) return AssemCJump.LT;
		else if(op == LTE) return AssemCJump.LE;
		else if(op == GTE) return AssemCJump.GE;
		return -1;
	}
	
	public AssemSeq translateSeqStatement(Stack seq)
	{
		AssemSeq retAssemType = null;
		
		while(!seq.isEmpty())
		{
			AssemType temp = (AssemType)seq.pop();
			// If there is an expression in this sequence, coerce it to a statement,
			// since we are building a list of statements 
			if(temp instanceof AssemExp)
			{
				AssemExp exp = (AssemExp) temp;
				temp = exp.translateToStatement();
			}
			retAssemType  = new AssemSeq((AssemStm)temp, (AssemStm)retAssemType);
		}
		
		return retAssemType;
	}
	
	public AssemExp build(AssemExpList list)
	{
		return new AssemBinOpExp(op, list.getHead(),list.getTail().getHead());
	}
	
	public AssemSeqExp canonize()
	{
		AssemSeqExp seLeft = left.canonize();
		AssemSeqExp seRight = right.canonize();	
		
		AssemStm seLeftStm = seLeft.getStm();
		AssemStm seRightStm = seRight.getStm();
		
		AssemExp seLeftExp = seLeft.getExp();
		AssemExp seRightExp = seRight.getExp();
		
		AssemSeqExp retSeqExp = null;
		if(commutes(seRightStm, seLeftExp))
		{
			// Order does not matter
			AssemStm stm = null;
			if(seLeftStm == null)
			{
				if(seRightStm != null) stm = seRightStm;
			}
			else
			{
				if(seRight == null) stm = seLeftStm;
				else
				{
					stm = new AssemSeq(seLeftStm, seRightStm);
				}
			}
			
			retSeqExp = new AssemSeqExp(stm, 
					new AssemBinOpExp(op, seLeftExp, seRightExp));
		}
		else
		{
			// Order matters; the left expression must be evaluated before the right statement
			Temp temp = new Temp();
			AssemTemp assemTemp = new AssemTemp(temp);
			AssemStm stm = null;
			AssemMove moveStm = new AssemMove(assemTemp,seLeftExp );
			
			if(seLeftStm == null)
			{
				if(seRightStm != null) 
				{
					stm = new AssemSeq(moveStm, seRightStm);
					retSeqExp = new AssemSeqExp(stm, 
							new AssemBinOpExp(op, assemTemp, seRightExp));
				}
			}
			else
			{
				if(seRightStm == null) 
				{
					stm = new AssemSeq(seLeftStm, moveStm);
				}
				else
				{
					stm = new AssemSeq(seLeftStm, new AssemSeq(moveStm, seRightStm));
					retSeqExp = new AssemSeqExp(stm, 
							new AssemBinOpExp(op, assemTemp, seRightExp));
				}
			}
		}
		return retSeqExp;
	}
	
	public boolean commutes(AssemStm stm, AssemExp exp)
	{
		return (stm == null) || (exp instanceof AssemName) || (exp instanceof AssemConst);
	}
	
	public int getExpType()
	{
		return AssemExp.BIN_OP;
	}
}
